import python
import semmle.python.dataflow.TaintTracking

class SimpleTest extends TaintKind {
    SimpleTest() { this = "simple.test" }
}

class SimpleSink extends TaintSink {
    override string toString() { result = "Simple sink" }

    SimpleSink() {
        exists(CallNode call |
            call.getFunction().(NameNode).getId() = "SINK" and
            this = call.getAnArg()
        )
    }

    override predicate sinks(TaintKind taint) { taint instanceof SimpleTest }
}

class SimpleSource extends TaintSource {
    SimpleSource() { this.(NameNode).getId() = "SOURCE" }

    override predicate isSourceOf(TaintKind kind) { kind instanceof SimpleTest }

    override string toString() { result = "simple.source" }
}

class SimpleSanitizer extends Sanitizer {
    SimpleSanitizer() { this = "Simple sanitizer" }

    override predicate sanitizingNode(TaintKind taint, ControlFlowNode node) {
        node.(CallNode).getFunction().(NameNode).getId() = "SANITIZE" and
        taint instanceof SimpleTest
    }

    override predicate sanitizingDefinition(TaintKind taint, EssaDefinition def) {
        exists(CallNode call |
            def.(ArgumentRefinement).getInput().getAUse() = call.getAnArg() and
            call.getFunction().(NameNode).getId() = "SANITIZE"
        ) and
        taint instanceof SimpleTest
    }
}

class BasicCustomTaint extends TaintKind {
    BasicCustomTaint() { this = "basic.custom" }

    override TaintKind getTaintForFlowStep(ControlFlowNode fromnode, ControlFlowNode tonode) {
        tonode.(CallNode).getAnArg() = fromnode and
        tonode.(CallNode).getFunction().(NameNode).getId() = "TAINT_FROM_ARG" and
        result = this
    }
}

class BasicCustomSink extends TaintSink {
    override string toString() { result = "Basic custom sink" }

    BasicCustomSink() {
        exists(CallNode call |
            call.getFunction().(NameNode).getId() = "CUSTOM_SINK" and
            this = call.getAnArg()
        )
    }

    override predicate sinks(TaintKind taint) { taint instanceof BasicCustomTaint }
}

class BasicCustomSource extends TaintSource {
    BasicCustomSource() { this.(NameNode).getId() = "CUSTOM_SOURCE" }

    override predicate isSourceOf(TaintKind kind) { kind instanceof BasicCustomTaint }

    override string toString() { result = "basic.custom.source" }
}

class Rock extends TaintKind {
    Rock() { this = "rock" }

    override TaintKind getTaintOfMethodResult(string name) {
        name = "prev" and result instanceof Scissors
    }

    predicate isSink(ControlFlowNode sink) {
        exists(CallNode call |
            call.getArg(0) = sink and
            call.getFunction().(NameNode).getId() = "paper"
        )
    }
}

class Paper extends TaintKind {
    Paper() { this = "paper" }

    override TaintKind getTaintOfMethodResult(string name) {
        name = "prev" and result instanceof Rock
    }

    predicate isSink(ControlFlowNode sink) {
        exists(CallNode call |
            call.getArg(0) = sink and
            call.getFunction().(NameNode).getId() = "scissors"
        )
    }
}

class Scissors extends TaintKind {
    Scissors() { this = "scissors" }

    override TaintKind getTaintOfMethodResult(string name) {
        name = "prev" and result instanceof Paper
    }

    predicate isSink(ControlFlowNode sink) {
        exists(CallNode call |
            call.getArg(0) = sink and
            call.getFunction().(NameNode).getId() = "rock"
        )
    }
}

class RockPaperScissorSource extends TaintSource {
    RockPaperScissorSource() {
        exists(string name | this.(NameNode).getId() = name |
            name = "ROCK" or name = "PAPER" or name = "SCISSORS"
        )
    }

    override predicate isSourceOf(TaintKind kind) { kind = this.(NameNode).getId().toLowerCase() }

    override string toString() { result = "rock.paper.scissors.source" }
}

private predicate function_param(string funcname, ControlFlowNode arg) {
    exists(FunctionObject f |
        f.getName() = funcname and
        arg = f.getArgumentForCall(_, _)
    )
}

class RockPaperScissorSink extends TaintSink {
    RockPaperScissorSink() {
        exists(string name | function_param(name, this) |
            name = "rock" or name = "paper" or name = "scissors"
        )
    }

    override predicate sinks(TaintKind taint) {
        exists(string name | function_param(name, this) |
            name = "paper" and taint = "rock"
            or
            name = "rock" and taint = "scissors"
            or
            name = "scissors" and taint = "paper"
        )
    }

    override string toString() { result = "rock.paper.scissors.sink" }
}

class TaintCarrier extends TaintKind {
    TaintCarrier() { this = "explicit.carrier" }

    override TaintKind getTaintOfMethodResult(string name) {
        name = "get_taint" and result instanceof SimpleTest
    }
}

/* There is no sink for `TaintCarrier`. It is not "dangerous" in itself; it merely holds a `SimpleTest`. */
class TaintCarrierSource extends TaintSource {
    TaintCarrierSource() { this.(NameNode).getId() = "TAINT_CARRIER_SOURCE" }

    override predicate isSourceOf(TaintKind kind) { kind instanceof TaintCarrier }

    override string toString() { result = "taint.carrier.source" }
}

/* Some more realistic examples */
abstract class UserInput extends TaintKind {
    bindingset[this]
    UserInput() { any() }
}

class UserInputSource extends TaintSource {
    UserInputSource() { this.(CallNode).getFunction().(NameNode).getId() = "user_input" }

    override predicate isSourceOf(TaintKind kind) { kind instanceof UserInput }

    override string toString() { result = "user.input.source" }
}

class SqlInjectionTaint extends UserInput {
    SqlInjectionTaint() { this = "SQL injection" }
}

class CommandInjectionTaint extends UserInput {
    CommandInjectionTaint() { this = "Command injection" }
}

class SqlSanitizer extends Sanitizer {
    SqlSanitizer() { this = "SQL sanitizer" }

    /** Holds if `test` shows value to be untainted with `taint` */
    override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
        exists(FunctionObject f, CallNode call |
            f.getName() = "isEscapedSql" and
            test.getTest() = call and
            call.getAnArg() = test.getSourceVariable().getAUse() and
            f.getACall() = call and
            test.getSense() = true
        ) and
        taint instanceof SqlInjectionTaint
    }
}

class CommandSanitizer extends Sanitizer {
    CommandSanitizer() { this = "Command sanitizer" }

    /** Holds if `test` shows value to be untainted with `taint` */
    override predicate sanitizingEdge(TaintKind taint, PyEdgeRefinement test) {
        exists(FunctionObject f |
            f.getName() = "isValidCommand" and
            f.getACall().(CallNode).getAnArg() = test.getSourceVariable().getAUse() and
            test.getSense() = true
        ) and
        taint instanceof CommandInjectionTaint
    }
}

class SqlQuery extends TaintSink {
    SqlQuery() {
        exists(CallNode call |
            call.getFunction().(NameNode).getId() = "sql_query" and
            call.getAnArg() = this
        )
    }

    override string toString() { result = "SQL query" }

    override predicate sinks(TaintKind taint) { taint instanceof SqlInjectionTaint }
}

class OsCommand extends TaintSink {
    OsCommand() {
        exists(CallNode call |
            call.getFunction().(NameNode).getId() = "os_command" and
            call.getAnArg() = this
        )
    }

    override string toString() { result = "OS command" }

    override predicate sinks(TaintKind taint) { taint instanceof CommandInjectionTaint }
}

class Falsey extends TaintKind {
    Falsey() { this = "falsey" }

    override boolean booleanValue() { result = false }
}

class FalseySource extends TaintSource {
    FalseySource() { this.(NameNode).getId() = "FALSEY" }

    override predicate isSourceOf(TaintKind kind) { kind instanceof Falsey }

    override string toString() { result = "falsey.source" }
}

class TaintIterable extends TaintKind {
    TaintIterable() { this = "iterable.simple" }

    override TaintKind getTaintForIteration() { result instanceof SimpleTest }
}

class TaintIterableSource extends TaintSource {
    TaintIterableSource() { this.(NameNode).getId() = "ITERABLE_SOURCE" }

    override predicate isSourceOf(TaintKind kind) { kind instanceof TaintIterable }
}
